package com.atguigu.gmall.item.service.impl;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.util.Jsons;
import com.atguigu.gmall.item.feign.SkuDetailFeignClient;
import com.atguigu.gmall.item.service.CacheOpsService;
import com.atguigu.gmall.product.entity.SkuImage;
import com.atguigu.gmall.product.entity.SpuSaleAttr;
import com.atguigu.gmall.product.entity.SkuInfo;
import com.atguigu.gmall.web.SkuDetailVo.CategoryView;

import com.atguigu.gmall.item.service.SkuDetailService;
import com.atguigu.gmall.web.SkuDetailVo;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import jdk.nashorn.internal.ir.IfNode;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;

/**
 * ClassName: SkuDetailServiceImpl
 * Package: com.atguigu.gmall.item.service.impl
 * Description:
 *
 * @Author ltystart
 * @Create 2025/8/23 15:25
 * @Version 1.0
 */
@Slf4j
@Service
public class SkuDetailServiceImpl implements SkuDetailService {
    @Autowired
    private SkuDetailFeignClient skuDetailFeignClient;

    private BloomFilter<Long> bloomFilter = null;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redisson;

    @Autowired
    private CacheOpsService cacheOpsService;

    /**
     * 原理：AQS基于CAS
     */
//    private ReentrantLock lock = new ReentrantLock();

    /**
     * 这个组件创建好以后会执行这个方法
     */
    @PostConstruct
    void init() {
        log.info("项目启动：正在初始化布隆过滤器");
        bloomFilter = BloomFilter.create(Funnels.longFunnel(), 1000000, 0.0000001);

        //获取所有skuId
        Result<List<Long>> SkuIds = skuDetailFeignClient.getAllSkuId();
        SkuIds.getData().forEach(item -> {
            bloomFilter.put(item);
        });
        log.info("布隆过滤器初始化完成... 48:{},77:{}", bloomFilter.mightContain(48L), bloomFilter.mightContain(77L));
    }

    /**
     * 查询商品详情
     * 分布式锁+布隆过滤器
     * 加锁：都去redis占坑。占坑+过期时间（自动解锁）=原子命令
     * 解锁：防止解掉别人的锁   比较锁值 + 删除锁 = 原子操作
     *
     * @param skuId
     * @return
     */
    @Override
    public SkuDetailVo skuDetail(Long skuId) {
        String key = RedisConst.SKU_DETAIL_CACHE_PREFIX + skuId;
        //1.先查询缓存
        SkuDetailVo data = cacheOpsService.getCache(key, SkuDetailVo.class);
        //2.判断是否存在
        if (data != null) {
            //3.缓存中有直接返回
            return data;
        }

        //4.缓存中没有:回源（缓存穿透，缓存击穿
        boolean contain = bloomFilter.mightContain(skuId);
        if (!contain) {
            return null;
        }

        //5.100w请求问布隆过滤器49号商品是否存在？如果存在，就回源查询数据（注意解决击穿风险）
        //6.准备一个分布式锁解决击穿，同一个商品只能被一个线程访问，注意：要设计为细粒度的锁
        RLock lock = redisson.getLock(RedisConst.LOCK_PREFIX + skuId);//lock-49
        //7.尝试获取锁
        boolean tryLock = lock.tryLock();
        if (tryLock) {
            try {
                //8.拿到锁：回源查询数据；双检查机制
                SkuDetailVo cache = cacheOpsService.getCache(key, SkuDetailVo.class);
                if (cache == null) {
                    //10.缓存中没有，回源查询数据
                    log.info("商品详情回源：{}", skuId);
                    SkuDetailVo fromRpc = getSkuDetailFromRpc(skuId);
                    cache = fromRpc;
                    //11.放入缓存
                    cacheOpsService.saveData(key, cache, 7L, TimeUnit.DAYS);
                }
                return cache;
            } finally {
                //12.释放锁
                lock.unlock();
            }
        } else {
            try {
                //9.没拿到锁：等待500ms，直接获取缓存中的数据
                Thread.sleep(500);
                return cacheOpsService.getCache(key, SkuDetailVo.class);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 查询商品详情
     * 分布式锁+lua脚本
     * 加锁：都去redis占坑。占坑+过期时间（自动解锁）=原子命令
     * 解锁：防止解掉别人的锁   比较锁值 + 删除锁 = 原子操作
     * @param skuId
     * @return
     */
//    @Override
//    public SkuDetailVo skuDetail(Long skuId) {
//        log.info("查询商品详情开始：{}", skuId);
//        String token = UUID.randomUUID().toString();
//        //1、加锁：都去redis占坑。占坑+过期时间（自动解锁）=原子命令
//        //Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","1");
//        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", token, 30, TimeUnit.SECONDS);
//        if (lock) {
//            //抢锁成功，回源查询数据
//            //redisTemplate.expire（Lock"，10,TimeUnit.SEcoNDS)；这样不原子
//            System.out.println("执行业务");
//
//            //业务期间断电。只要redis能自动解锁，就不怕业务断电。
//            //业务超长、临界状态，可能都会导致删除别人的锁
//            //自动续期：思路？复制小括号，写死右箭头，落地大括号
//
////            Thread thread = new Thread(()->{
////                //每隔10s，续到30
////                while (true){
////                    Thread.sleep(10000);
////                    redisTemplate.expire("lock",30,TimeUnit.SECONDS);
////                }
////            });
////            thread.setDaemon(true); //守护
//
//            //自动续期+可重入锁？ 所有的锁都应该设计为 可重入的，否则可能会有死锁问题
//            // a () {
//            //   b() {
//            //   }
//            // }
//
//            //2、解锁：防止解掉别人的锁   比较锁值 + 删除锁 = 原子操作
////            String lockValue = redisTemplate.opsForValue().get("lock");
////            if(token.equals(lockValue)){
////                redisTemplate.delete("lock");
////            }
//            //redis单线程  "100行"  lua 脚本  if redis.call(get,"lock") == uuid then redis.call("lock")
//            String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
//                    "then\n" +
//                    "    return redis.call(\"del\",KEYS[1])\n" +
//                    "else\n" +
//                    "    return 0\n" +
//                    "end";
//            //返回0，代表删锁失败：1）、锁已经被删了  2）、锁是别人的锁
//            //返回1，删锁成功，删除了自己的锁
//            Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
//                    Arrays.asList("lock"), token);
//            if (result == 0) {
//                log.error("这个锁是别人的，不能删");
//            } else {
//                log.info("删除了自己的锁");
//            }
//
//        } else {
//            //抢锁失败。等待500ms，重新查询缓存
//            try {
//                Thread.sleep(500);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }
//        //1000w并发进来，查询了 1~1000w的所有商品
//
//        return null;
//    }


    /**
     * 加入缓存 5000000 600w+
     * 1、0bject data = cache.get（skuId)//88-x600xxX-X
     * if（data ==null）//回源
     * if（skuId.contains（skuId)）//数据库中有此记录 回源 else return nulL;
     * 有：return data;
     *
     * 使用布隆过滤器，添加本地锁
     * @param skuId
     * @return
     */
//    @Override
//    public SkuDetailVo skuDetail(Long skuId) {
//        log.info("查询商品详情开始：{}", skuId);
//        if (!bloomFilter.mightContain(skuId)){
//            //1.布隆过滤器没有
//            log.info("商品详情-布隆过滤器拦截-数据库中没有直接返回null：{}",skuId);
//            return null;
//        }
//
//        //2.先查缓存
//        log.info("商品详情-正在数据回源：{}", skuId);
//        String json = redisTemplate.opsForValue().get("sku:info:" + skuId);
//
//        if (!StringUtils.isEmpty( json)){
//            //3.redis中有缓存数据；x或真实值
//            SkuDetailVo detailVo = Jsons.toObj(json, SkuDetailVo.class);
//            return detailVo;
//        }
//
//        boolean tryLock = lock.tryLock();
//        if (tryLock){
//            //加锁
//            //4.redis中没有缓存数据
//            SkuDetailVo fromRpc = getSkuDetailFromRpc(skuId);
//            //把对象转为 json字符串
//            String data = fromRpc==null?"x":Jsons.toStr(fromRpc);
//            //保存到redis
//            redisTemplate.opsForValue().set("sku:info:"+skuId,data);
//            log.info("商品详情-数据回源完成-并已保存到redis：{}", fromRpc);
//            lock.unlock();
//            return fromRpc;
//        }else {
//            try {
//                Thread.sleep(500);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            //直接查缓存即可
//            json = redisTemplate.opsForValue().get("sku:info:" + skuId);
//            return Jsons.toObj(json, SkuDetailVo.class);
//        }
//    }

    /**
     * 从远程查询商品信息
     *
     * @param skuId
     * @return
     */
    private SkuDetailVo getSkuDetailFromRpc(Long skuId) {
        SkuDetailVo vo = new SkuDetailVo();
        //1.获取sku信息
        Result<SkuInfo> skuInfo = skuDetailFeignClient.getSkuInfo(skuId);

        //2.查询当前sku的图片列表
        Result<List<SkuImage>> skuImages = skuDetailFeignClient.getSkuImages(skuId);
        SkuInfo info = skuInfo.getData();
        info.setSkuImageList(skuImages.getData());
        //设置好skuinfo的数据返回
        vo.setSkuInfo(info);

        //3. 获取分类信息
        Result<CategoryView> categoryView = skuDetailFeignClient.getCategoryView(info.getCategory3Id());
        vo.setCategoryView(categoryView.getData());

        //4. 获取sku价格
        Result<BigDecimal> skuPrice = skuDetailFeignClient.getSkuPrice(skuId);
        vo.setPrice(skuPrice.getData());

        //5. 获取sku的销售属性信息
        Long spuId = info.getSpuId();
        Result<List<SpuSaleAttr>> value = skuDetailFeignClient.getSpuSaleAttrAndValue(skuId, spuId);
        vo.setSpuSaleAttrList(value.getData());

        //6、js0n就是map（"119|120":49,"122:123":50）、javaBean
        Result<String> skuJson = skuDetailFeignClient.getSpuValuesSkuJson(spuId);
        vo.setValuesSkuJson(skuJson.getData());

        return vo;
    }
}
